/*
** Copyright (C) 2009 Joshua Brent Marsh <joshua@icub3d.com>
**  
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**  
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**  
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**  
*/

/* $Id: buffer.c 9 2009-10-08 15:50:15Z joshua@icub3d.com $ */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>

#include "buffer.h"

#define MARK_DISCARDED -1

Buffer * 
buffer_create_helper (void *data, size_t size)
{
  Buffer * buf;

  assert (data != NULL);

  /* Create the buffer object. */
  buf = malloc (sizeof (Buffer));
  if (buf == NULL)
    {
      perror ("malloc buffer");
      return NULL;
    }

  buf->data = data;
  buf->size = size;

  return buf;
}

Buffer *
buffer_create (size_t size)
{
  Buffer *buf;
  void *data;

  /* Allocate the memory for the buffer. */
  data = malloc (size);
  if (data == NULL)
    {
      perror ("malloc buffer->data");
      return NULL;
    }

  buf = buffer_create_helper (data, size);
  if (buf == NULL) 
    return NULL;

  /* Initialize it. */
  buf->position = 0;
  buf->limit = buf->size;
  buf->mark = MARK_DISCARDED;

  return buf;
}

void
buffer_destroy (Buffer *buf, int destroy)
{
  assert (buf != NULL);

  if (destroy)
    free (buf->data);

  free (buf);
}

size_t 
buffer_size (Buffer *buf)
{
  assert (buf != NULL);

  return buf->size;
}

void 
buffer_compact (Buffer *buf)
{
  size_t dist;

  assert (buf != NULL);

  if (buf->position == 0)
    return;

  dist = buffer_remaining (buf);
  
  buffer_get (buf, buf->data, dist);
  buf->position = dist;
  buf->limit = buf->size;
  buf->mark = MARK_DISCARDED;
}

void
buffer_clear (Buffer *buf)
{
  assert (buf != NULL);

  buf->position = 0;
  buf->limit = buf->size;
  buf->mark = MARK_DISCARDED;
}

void
buffer_flip (Buffer *buf)
{
  assert (buf != NULL);

  buf->limit = buf->position;
  buf->position = 0;
  buf->mark = MARK_DISCARDED;
}

void
buffer_mark (Buffer *buf)
{
  assert (buf != NULL);
  
  buf->mark = buf->position;
}

void
buffer_reset (Buffer *buf)
{
  assert (buf != NULL);
  
  if (buf->mark != MARK_DISCARDED)
    buf->position = buf->mark;
}

void 
buffer_rewind (Buffer *buf)
{
  assert (buf != NULL);
  
  buf->position = 0;
  buf->mark = MARK_DISCARDED;
}

size_t 
buffer_remaining (Buffer *buf)
{
  assert (buf != NULL);
  
  return buf->limit - buf->position;
}

size_t 
buffer_get_limit (Buffer *buf)
{
  assert (buf != NULL);
  
  return buf->limit;
}

void buffer_set_limit (Buffer *buf, size_t limit)
{
  assert (buf != NULL);
  assert (limit <= buf->size);
  assert (limit >= 0);

  if (limit > buf->size || limit < 0)
    return;

  buf->limit = limit;

  if (buf->mark > limit)
    buf->mark = MARK_DISCARDED;

  if (buf->position > limit)
    buf->position = limit;

}

size_t 
buffer_get_position (Buffer *buf)
{
  assert (buf != NULL);

  return buf->position;
}

void buffer_set_position (Buffer *buf, size_t position)
{
  assert (buf != NULL);
  assert (position <= buf->limit);
  assert (position >= 0);

  if (position > buf->size || position < 0)
    return;

  if (buf->mark > position)
    buf->mark = MARK_DISCARDED;

  buf->position = position;
}

size_t 
buffer_get (Buffer *buf, void *dest, size_t size)
{
  size_t min;

  assert (buf != NULL);

  min = buffer_remaining (buf);
  if (min > size)
    min = size;

  memmove ((char *)buf->data + buf->position, dest, min);
  buf->position = buf->position + min;

  return min;
}

size_t 
buffer_get_abs (Buffer *buf, void *dest, size_t size, size_t start)
{
  size_t min;

  assert (buf != NULL);
  assert (start < buf->size);

  min = buf->size - start;
  if (min > size)
    min = size;

  memmove ((char *)buf->data + start, dest, min);

  return min;
}

size_t 
buffer_put (Buffer *buf, void *src, size_t size)
{
  size_t min;

  assert (buf != NULL);

  min = buffer_remaining (buf);
  if (min > size)
    min = size;

  memmove (src, (char *)buf->data + buf->position, min);
  buf->position = buf->position + min;

  return min;
}

size_t 
buffer_put_abs (Buffer *buf, void *src, size_t size, size_t start)
{
  size_t min;

  assert (buf != NULL);
  assert (start < buf->size);

  min = buf->size - start;
  if (min > size)
    min = size;

  memmove (src, (char *)buf->data + start, min);

  return min;
}

Buffer *
buffer_slice (Buffer *buf)
{
  Buffer * nbuf;

  assert (buf != NULL);

  nbuf = buffer_create_helper ((char *)buf->data + buf->position, 
			       buffer_remaining (buf));
  if (nbuf == NULL) 
    return NULL;

  nbuf->position = 0;
  nbuf->limit = nbuf->size;
  nbuf->mark = MARK_DISCARDED;

  return nbuf;  
}

Buffer *
buffer_wrap (void *data, size_t size)
{
  Buffer * buf;

  assert (data != NULL);

  buf = buffer_create_helper (data, size);
  if (buf == NULL) 
    return NULL;

  buf->position = 0;
  buf->limit = buf->size;
  buf->mark = MARK_DISCARDED;

  return buf;  
}

Buffer *
buffer_dup (Buffer *buf)
{
  Buffer * nbuf;

  assert (buf != NULL);

  nbuf = buffer_create_helper (buf->data, buf->size);
  if (nbuf == NULL) 
    return NULL;

  nbuf->position = buf->position;
  nbuf->limit = buf->limit;
  nbuf->mark = buf->mark;

  return nbuf;  
}

void *
buffer_array (Buffer *buf)
{
  assert (buf != NULL);

  return buf->data;
}

Buffer *
buffer_copy (Buffer *buf)
{
  Buffer *nbuf;

  assert (buf != NULL);

  nbuf = buffer_create (buf->size);
  if (nbuf ==NULL)
    return NULL;
  
  memcpy (nbuf->data, buf->data, nbuf->size);
  
  return nbuf;

}

ssize_t
buffer_read (Buffer *buf, ssize_t *sum, int fd)
{
  ssize_t cur;

  *sum = 0;

  while ((cur = read (fd, (char *)buf->data + buf->position, 
		      buffer_remaining (buf))) > 0)
    {
      *sum = *sum + cur;
      buf->position = buf->position + cur;
    }

  return cur;
}

ssize_t
buffer_write (Buffer *buf, ssize_t *sum, int fd)
{
  ssize_t cur;

  *sum = 0;

  while ((cur = write (fd, (char *)buf->data + buf->position, 
		       buffer_remaining (buf))) > 0)
    {
      *sum = *sum + cur;
      buf->position = buf->position + cur;
    }

  return cur;
}

ssize_t 
buffer_fread (Buffer *buf, ssize_t *size, FILE *fd)
{
  int fid;
  fid = fileno (fd);
  if (fid == -1)
    return -1;

  return buffer_read (buf, size, fid);
}

ssize_t 
buffer_fwrite (Buffer *buf, ssize_t *size, FILE *fd)
{
  int fid;
  fid = fileno (fd);
  if (fid == -1)
    return -1;

  return buffer_write (buf, size, fid);
}
